// 首先，Item类与ShoppingCart类中都有一个name属性，在Item中表示商品名称，在ShoppingCart中则表示用户名；
// 然后，Item类中有一个price属性，ShoppingCart中有一个price方法，它们都表示当前的价格。
// 考虑到name属性与price属性（方法）的名称与类型完全一致，在信息表达上的作用也几乎一致，因此我可以在这两个类的基础上，再抽象出一个新的基类Meta，用于存放price属性与name属性。
class Meta {
  double price;
  String name;
  Meta(this.name, this.price);
}
// 关于购物车信息的打印，我们是通过在main函数中获取到购物车对象的信息后，使用全局的print函数打印的，我们希望把打印信息的行为封装到ShoppingCart类中。
// 而对于打印信息的行为而言，这是一个非常通用的功能，不止ShoppingCart类需要，Item对象也可能需要。
// 因此，我们需要把打印信息的能力单独封装成一个单独的类PrintHelper。
// 但，ShoppingCart类本身已经继承自Meta类，考虑到Dart并不支持多继承，所以这里使用“混入”（Mixin）。
mixin PrintHelper {
  printInfo() => print(getInfo());
  getInfo();
}
// 定义商品Item类
class Item extends Meta with PrintHelper {
  num number;
  Item(super.name, super.price, this.number);
  // 重载了+运算符，合并商品为套餐商品，就是将两个实例的name和price做+运算，得到一个新的实例，新实例的价格就是累加的结果
  Item operator+(Item item) => Item(name + item.name, price * number + item.price, 1);

  @override
  getInfo () => '''
    -------------------------
    商品名称: $name
    商品价格: $price
    商品数量: $number
    -------------------------
  ''';
}

// 定义购物车类
class ShoppingCart extends Meta with PrintHelper {
  DateTime date;
  String code;
  List<Item> bookings = [];

  @override
  // 考虑到在ShoppingCart类中，price属性仅用做计算购物车中商品的价格（而不是像Item类那样用于数据存取），因此在继承了Meta类后，改写了ShoppingCart类中price属性的get方法
  // 把迭代求和改写为归纳合并
  // value + element中的运算符已被Item类重载，所以对象之间可以直接使用运算
  double get price => bookings.reduce((value, element) => value + element).price;

  num get number => bookings.length;

  String get goodsInfo => bookings.map((item) => item.getInfo()).join('\n');

  // 提供给调用者更明确的初始化方法调用方式，让调用者以“参数名:参数键值对”的方式指定调用参数，让调用者明确传递的初始化参数的意义。
  // 对一个购物车对象来说，一定会有一个有用户名，但不一定有优惠码的用户。
  // 因此，对于购物车对象的初始化，我们还需要提供一个不含优惠码的初始化方法，并且需要确定多个初始化方法与父类的初始化方法之间的正确调用顺序。
  // 默认初始化方法，转发到withCode里
  ShoppingCart({name}) : this.withCode(name:name, code:'');
  // withCode初始化方法，使用语法糖和初始化列表进行赋值，并调用父类初始化方法
  ShoppingCart.withCode({name, required this.code}) : date = DateTime.now(), super(name,0);

  @override
  getInfo () => '''
购物车信息:
-----------------------------
  用户名: $name
  优惠码: ${code == ''?"没有":code}
  商品详情:

$goodsInfo
  商品总数: $number
  总价: $price
  Date: $date
-----------------------------
''';
}

void main() {
  // 使用级联运算符“..”，在同一个对象上连续调用多个函数以及访问成员变量。使用级联操作符可以避免创建临时变量，让代码看起来更流畅。
  ShoppingCart.withCode(name:'张三', code:'123456')
    ..bookings = [Item('苹果', 10.0, 1), Item('鸭梨', 20.0, 2)]
    ..printInfo();

  ShoppingCart(name:'李四')
    ..bookings = [Item('香蕉', 15.0, 3), Item('西瓜', 40.0, 1)]
    ..printInfo();
}